-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Val err details #12
Val err details #12
Conversation
- ValidationError should have more detail. - ValidationError::message should maybe have a String so people can use format! to generate the messages.
Looks like my merge went completely wrong. |
I reverted the mis-merge of the master branch. |
Maybe this closes #5 ? |
It looks like you're deleting |
Why not use |
I wonder if the fields of the |
I also think we should add |
Although all of this is starting to be a bit messy. Looking from a UX point of view, the error should say something like |
Another idea: we can create two separate default errors for construction/mutation:
|
I'll sort this out later today, I'm just starting my day here 😉
I was wondering the same. I see this code as a first draft. In the case of this type, making the fields private makes sense as who knows how we want to represent the source_type in future. I also think the we may want to make the
I'll take a look at your proposed solutions later on. But one way or the other having access to the input value is critical for a decent error message. |
Of course :)
Fair
I thought about the |
remove the period.
- String allows for error messages constructed using format! and such macros.
I like the idea of separate errors for the separate actions. I'll take a look at adding the value now. |
- The message field is removed as there is no need for custom error messages yet. - The type alias is passed given to the Error so it can print it.
README.md
Outdated
@@ -47,12 +47,12 @@ let mut u = Username::new(" valid name \n\n").unwrap(); | |||
assert_eq!(u.get(), "valid name"); // now we're talking! | |||
|
|||
// This also works for mutations: | |||
assert!(matches!(u.try_mutate(|u| *u = " ".to_owned()), Err(prae::ValidationError))); | |||
assert!(matches!(u.try_mutate(|u| *u = " ".to_owned()), Err(prae::ConstructionError<String>))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't it be MutationError<String>
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea I didn't add the mutation error yet
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I want to argue that the generic bound E
should be removed. See the comment below.
With the current arch of the
I think that the
I think I would like to capture errors returned from the
That would generate an error:
|
prae/tests/ensure.rs
Outdated
un.try_mutate(|u| *u = "".to_owned()).unwrap_err(), | ||
prae::ValidationError | ||
); | ||
let _error: prae::ConstructionError<String> = un.try_mutate(|u| *u = "".to_owned()).unwrap_err(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not assert_matches!
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll put assert_matches back in. I was having trouble getting the match to compile using the generic param, but all I need is a turbo fish.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The assert_matches!
are restored.
prae/src/core.rs
Outdated
T: fmt::Debug | ||
{ | ||
/// The name of the type where this ConstructionError originated. | ||
guarded_type_name: String, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure it should be String
since we know the type during compile time
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I want to print the alias, how would I store that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The type is quite a mouthful 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but.. it could be a &'static str
...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice catch, the change is pushed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I meant the &'static str
, sorry for not being clear
Good point. I agree.
I don't think it should. The reason why it's generic is because it's meant to be as flexible as possible and return any error. The Also, I have an idea about possible design that will allow user to return custom messages, but I didn't finish it so I also do it a bit later (haven't much time today :)). Anyway, thank you for your help, we'll finish it soon :) |
All good, get some rest 😉 We'll talk about it soon. |
So, I looked at it and it seems to me that bringing Possible solution will be to split the I think right now we can just revert to the HEAD and add information about the type to the pub struct ValidationError<T> {
_ty: PhantomData<T>,
}
impl<T> fmt::Debug for ValidationError<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(
f,
"failed to construct/mutate {}: provided value is not valid",
std::any::type_name::<T>()
)
}
}
impl<T> fmt::Display for ValidationError<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
<Self as fmt::Debug>::fmt(self, f)
}
}
impl<T> std::error::Error for ValidationError<T> {} And change the tests to use I propose to focus on another topics right now and return to this later. The fact that a user will see the message What do you think? |
Although we could probably define |
- There was a plan to have two errors, one for Construction and another for Mutation. This is not going ahead due to it requiring major arch changes.
Agreed for the increase in complexity doesn't justify the value of having a I'm happy with the implementation as it is, a single |
I think this is also needless complexity for the value of having two Errors types. Let's keep it to one Error type until the need arises to make two. |
I would like to implement validate returning a |
#[cfg(test)] | ||
mod test_deps { | ||
use assert_matches as _; | ||
use serde as _; | ||
use serde_json as _; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Btw, why is this necessary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean the whole test_deps
business
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added the lint to warn about unused crate dependencies.
#![warn(unused_crate_dependencies)]
But it give's false positives when buliding the separate integration test binaries.
❯ cargo test
Compiling prae_macro v0.2.1 (/home/brianh/Projects/third_party/prae/prae_macro)
Compiling prae v0.4.5 (/home/brianh/Projects/third_party/prae/prae)
warning: external crate `assert_matches` unused in `prae`: remove the dependency or add `use assert_matches as _;`
|
note: the lint level is defined here
--> prae/src/lib.rs:71:9
|
71 | #![warn(unused_crate_dependencies)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^
= help: remove unnecessary dependency `assert_matches`
warning: external crate `serde` unused in `prae`: remove the dependency or add `use serde as _;`
|
= help: remove unnecessary dependency `serde`
warning: external crate `serde_json` unused in `prae`: remove the dependency or add `use serde_json as _;`
|
= help: remove unnecessary dependency `serde_json`
warning: 3 warnings emitted
Finished test [unoptimized + debuginfo] target(s) in 0.73s
Running unittests (target/debug/deps/prae-dc495198ddde61f3)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, now I get it. I'll add a comment referencing the related issue: rust-lang/rust#57274
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice 😉
prae_macro/src/lib.rs
Outdated
let f: fn(&Self::Target) -> bool = #closure; | ||
if f(v) { None } else { Some(prae::ValidationError) } | ||
if f(v) { None } else { Some(prae::ValidationError::<#ty>::new(stringify!(#ident), v.clone()) ) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if the type is not clone? In this case the user will use Guarded::mutate
instead of Guarded::try_mutate
, but they both use validate
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, exactly. The following code works on HEAD but not on this branch:
#[derive(Debug)]
struct User {
name: String,
}
prae::define! {
ValidUser: User
ensure |u: &User| !u.name.is_empty()
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's either clone, copy or have the error manage reference lifetimes. I would like to avoid having lifetimes as that constraint will leak into the users code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we can add a nicer message though, instead of a compile error emanating from the proc macro?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess it's also can't be done with the current code. Introducing ConstructionError<E>
and MutationError<E>
should solve the issue, since we'll be able to insert the value: T
inside Guarded::new
and Guarded::try_mutate
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see a way around cloning, as we have to clone at some point so the error can store the value, or.... you convert the value to a string which requires T: Debug
or T: DIsplay
.
Requiring Clone
on a type is a reasonable expectation given that it's trivial to implement and it's the exception that you see types that are not clone-able.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The latest commit removes the value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Making it T: Debug
and turning it into string is a reasonable workaround! But expecting the value to be always Clone
is a no go, since the library must be available for as many use cases as possible. Sometimes making your value !Clone
is a design choice, and we must tolerate this.
Do you want to add str_value: String
or should I do it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can do it. I'll update the non_clone test too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ValidationError::value
is reinstated as a String
, changes are pushed.
- The value would force the users type to implement `Clone`.
- This allows the error to store a value without a generic `T: Clone + Debug`
Perfect! |
I've added more detail to the
ValidationError
.ValidationError::source_type
field stores the name of the source typeValidationError::message
stores an error message.ValidationError::new::<T>(message)
fills in thesource_type
name based onT
and stores the message.PartialEq
(you should always pairPartialEq
andEq
) as it was only being used for equality comparisons in the integration tests. Usingassert_matches!
allows you to avoid equality tests as the pattern matching system does not use equality tests.